今天要來建立文章列表。這邊會使用到 useQuery()
這個方法,以及為了避免重複觸及 API 兩次的 useAsyncData()
方法。
首先新增 @/pages/articles/index.vue
<template>
<section class="pb-24">
<div class="divide-y divide-gray-200 dark:divide-gray-700">
<header class="pb-8 space-y-2 md:space-y-5">
<div>
<h1 class="tracking-tight font-extrabold text-gray-900 text-2xl leading-6 mb-1">
Aritcles
</h1>
</div>
</header>
<main>
<ul>
<li v-for="article in articles" :key="article._path" class="py-4">
<ListItem :item="article" />
</li>
</ul>
</main>
</div>
</section>
</template>
<script lang="ts" setup>
import { Post } from '@/types/index'
const slug = 'articles'
const { data } = await useAsyncData(slug, () =>
queryContent(slug)
.where({ slug: { $ne: slug }, _file: { $not: { $contains: 'index' } } })
.sort({ created_at: -1, published_at: -1 })
.find()
)
const post = data.value as Post[]
const articles = post.map((post) => {
const hasPostTags = !!post.tags
if (!hasPostTags) {
post.tags = []
}
return post
})
</script>
queryContent()
是協助我們查詢 content 資料夾檔案的方法,其中 slug
這個參數可以先視為要從其下的哪個路徑去查詢。後面則是搭配 MongoDB 的查詢語法去做篩選。
外面則再使用 useAsyncData()
包覆起來,並加上 await 來等待這份資料的查詢。
另外,會發現我將原本放在 @/components/TheArticles.vue
的介面抽出來放到了 @/index.d.ts
,讓我可複用這個介面。
在下方並且未有些可能不會有 tags 屬性的文章加上這個屬性,避免在後面因為未定義而出錯。
為了讓程式碼去可讀性,從這裡會將顯示列表的項目拆成另一個 component,位於 @/components/ListItem.vue
<template>
<article
class="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0"
>
<dl>
<dt class="sr-only">Published on</dt>
<dd
class="text-base font-medium leading-6 text-gray-500"
>
<time v-if="datetime" dateTime="{{datetime}}">
{{
DateTime.fromISO(datetime).toLocal().toFormat('yyyy-LL-dd')
}}
</time>
</dd>
</dl>
<div class="col-span-3">
<div>
<NuxtLink :href="item._path">
<h3
class="text-xl font-bold leading-8 tracking-tight text-gray-900"
>
{{ item.title }}
</h3>
</NuxtLink>
<div class="flex flex-wrap">
<PostTag v-for="tag in item.tags" :key="tag" :text="tag" />
</div>
</div>
</div>
</article>
</template>
<script setup lang="ts">
import { DateTime } from 'luxon'
import { Post } from '@/types/index'
const props = defineProps<{
item: Post
}>()
const datetime = props.item.published_at
? props.item.published_at
: props.item.created_at
</script>
最後再建立 @/components/PostTag.vue
來渲染標籤清單:
<template>
<NuxtLink
:href="'/tags/' + kebabCase(text)"
class="mr-3 text-sm font-medium uppercase text-teal-500 hover:text-teal-600"
>
{{ text.split(' ').join('-') }}
</NuxtLink>
</template>
<script setup>
import { kebabCase } from 'lodash-es/string'
defineProps({
text: {
type: String,
default: '',
},
})
</script>
成果如下圖: